{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 函数的文档"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你在调用函数的时候，你像是函数这个产品的用户。\n",
    "\n",
    "而你写一个函数，像是做一个产品，这个产品将来可能会被很多用户使用 —— 包括你自己。\n",
    "\n",
    "产品，就应该有产品说明书，别人用得着，你自己也用得着 —— 很久之后的你，很可能把当初的各种来龙去脉忘得一干二净，所以也同样需要产品说明书，别看那产品曾经是你自己设计的。\n",
    "\n",
    "Python 在这方面很用功，把函数的“产品说明书”当作语言内部的功能，这也是为什么 Python 有 [Sphinx](http://www.sphinx-doc.org) 这种工具，而绝大多数其他语言没有的原因之一罢。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Docstring"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在函数定义内部，我们可以加上 **Docstring**；将来函数的“用户”就可以通过 `help()` 这个内建函数，或者 `.__doc__` 这个 Method 去查看这个 Docstring，即，该函数的“产品说明书”。\n",
    "\n",
    "先看一个 Docstring 以及如何查看某个函数的 Docstring 的例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function is_prime in module __main__:\n",
      "\n",
      "is_prime(n)\n",
      "    Return a boolean value based upon\n",
      "    whether the argument n is a prime number.\n",
      "\n",
      "\n",
      "    Return a boolean value based upon\n",
      "    whether the argument n is a prime number.\n",
      "    \n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'\\n    Return a boolean value based upon\\n    whether the argument n is a prime number.\\n    '"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def is_prime(n):\n",
    "    \"\"\"\n",
    "    Return a boolean value based upon\n",
    "    whether the argument n is a prime number.\n",
    "    \"\"\"\n",
    "    if n < 2:\n",
    "        return False\n",
    "    if n == 2:\n",
    "        return True\n",
    "    for m in range(2, int(n**0.5)+1):\n",
    "        if (n % m) == 0:\n",
    "            return False\n",
    "    else:\n",
    "        return True\n",
    "\n",
    "\n",
    "help(is_prime)\n",
    "print(is_prime.__doc__)\n",
    "is_prime.__doc__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Docstring 可以是多行字符串，也可以是单行字符串："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function is_prime in module __main__:\n",
      "\n",
      "is_prime(n)\n",
      "    Return a boolean value based upon whether the argument n is a prime number.\n",
      "\n",
      "Return a boolean value based upon whether the argument n is a prime number.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Return a boolean value based upon whether the argument n is a prime number.'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def is_prime(n):\n",
    "    \"\"\"Return a boolean value based upon whether the argument n is a prime number.\"\"\"\n",
    "    \n",
    "    if n < 2:\n",
    "        return False\n",
    "    if n == 2:\n",
    "        return True\n",
    "    for m in range(2, int(n**0.5)+1):\n",
    "        if (n % m) == 0:\n",
    "            return False\n",
    "    else:\n",
    "        return True\n",
    "\n",
    "\n",
    "help(is_prime)\n",
    "print(is_prime.__doc__)\n",
    "is_prime.__doc__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Docstring 如若存在，必须在函数定义的内部语句块的开头，也必须与其它语句一样保持相应的缩进（Indention）。Docstring 放在其它地方不起作用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function is_prime in module __main__:\n",
      "\n",
      "is_prime(n)\n",
      "\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "def is_prime(n):\n",
    "    if n < 2:\n",
    "        return False\n",
    "    if n == 2:\n",
    "        return True\n",
    "    for m in range(2, int(n**0.5)+1):\n",
    "        if (n % m) == 0:\n",
    "            return False\n",
    "    else:\n",
    "        return True\n",
    "    \"\"\"\n",
    "    Return a boolean value based upon\n",
    "    whether the argument n is a prime number.\n",
    "    \"\"\"\n",
    "\n",
    "help(is_prime)\n",
    "print(is_prime.__doc__)\n",
    "is_prime.__doc__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 书写 Docstring 的规范"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "规范，虽然是人们最好遵守的，但其实通常是很多人并不遵守的东西。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "既然学，就要**像样** —— 这真的很重要。所以，非常有必要认真阅读 Python [PEP 257](https://www.python.org/dev/peps/pep-0257/) 关于 Docstring 的规范。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "简要总结一下 PEP 257 中必须掌握的规范：\n",
    "\n",
    "> 1. 无论是单行还是多行的 Docstring，一概使用三个双引号扩起；\n",
    "> 2. 在 Docstring 内部，文字开始之前，以及文字结束之后，都不要有空行；\n",
    "> 3. 多行 Docstring，第一行是概要，随后空一行，再写其它部分；\n",
    "> 4. 完善的 Docstring，应该概括清楚以下内容：参数、返回值、可能触发的错误类型、可能的副作用，以及函数的使用限制等等；\n",
    "> 5. 每个参数的说明都使用单独的一行……\n",
    "\n",
    "由于我们还没有开始研究 Class，所以，关于 Class 的 Docstring 应该遵守什么样的规范就暂时略过了。然而，这种规范你总是要反复去阅读参照的。关于 Docstring，有两个规范文件：\n",
    "\n",
    "> * [PEP 257: Docstring Convensions](https://www.python.org/dev/peps/pep-0257/)\n",
    "> * [PEP 258: Docutils Design Specification](https://www.python.org/dev/peps/pep-0258/)\n",
    "\n",
    "需要**格外注意**的是：\n",
    "\n",
    "> Docstring 是**写给人看的**，所以，在复杂代码的 Docstring 中，写 **Why** 要远比写 _What_ 更重要 —— 你先记住这点，以后的体会自然会不断加深。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sphinx 版本的 Docstring 规范"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sphinx 可以从 `.py` 文件里提取所有 Docstring，而后生成完整的 Documentation。将来若是你写大型的项目，需要生成完善的文档的时候，你就会发现 Sphinx 是个“救命”的家伙，省时、省力、省心、省命……\n",
    "\n",
    "在这里，没办法一下子讲清楚 Sphinx 的使用，尤其是它还用它自己的一种标记语言，reStructureText，文件尾缀使用 `.rst`……\n",
    "\n",
    "但是，可以看一个例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Vehicle in module __main__:\n",
      "\n",
      "class Vehicle(builtins.object)\n",
      " |  Vehicle(arg, *args, **kwargs)\n",
      " |  \n",
      " |  The Vehicle object contains lots of vehicles\n",
      " |  :param arg: The arg is used for ...\n",
      " |  :type arg: str\n",
      " |  :param `*args`: The variable arguments are used for ...\n",
      " |  :param `**kwargs`: The keyword arguments are used for ...\n",
      " |  :ivar arg: This is where we store arg\n",
      " |  :vartype arg: str\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, arg, *args, **kwargs)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  cars(self, distance, destination)\n",
      " |      We can't travel a certain distance in vehicles without fuels, so here's the fuels\n",
      " |      \n",
      " |      :param distance: The amount of distance traveled\n",
      " |      :type amount: int\n",
      " |      :param bool destinationReached: Should the fuels be refilled to cover required distance?\n",
      " |      :raises: :class:`RuntimeError`: Out of fuel\n",
      " |      \n",
      " |      :returns: A Car mileage\n",
      " |      :rtype: Cars\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "class Vehicle(object):\n",
    "    '''\n",
    "    The Vehicle object contains lots of vehicles\n",
    "    :param arg: The arg is used for ...\n",
    "    :type arg: str\n",
    "    :param `*args`: The variable arguments are used for ...\n",
    "    :param `**kwargs`: The keyword arguments are used for ...\n",
    "    :ivar arg: This is where we store arg\n",
    "    :vartype arg: str\n",
    "    '''\n",
    "\n",
    "\n",
    "    def __init__(self, arg, *args, **kwargs):\n",
    "        self.arg = arg\n",
    "\n",
    "    def cars(self, distance, destination):\n",
    "        '''We can't travel a certain distance in vehicles without fuels, so here's the fuels\n",
    "\n",
    "        :param distance: The amount of distance traveled\n",
    "        :type amount: int\n",
    "        :param bool destinationReached: Should the fuels be refilled to cover required distance?\n",
    "        :raises: :class:`RuntimeError`: Out of fuel\n",
    "\n",
    "        :returns: A Car mileage\n",
    "        :rtype: Cars\n",
    "        '''  \n",
    "        pass\n",
    "\n",
    "help(Vehicle)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过插件，Sphinx 也能支持 Google Style Docstring 和 Numpy Style Docstring。\n",
    "\n",
    "以下两个链接，放在这里，以便你将来查询：\n",
    "\n",
    "> * [sphinx.ext.napoleon – Support for NumPy and Google style docstrings](http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)\n",
    "> * [sphinx.ext.autodoc – Include documentation from docstrings](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  },
  "toc-autonumbering": true
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
